﻿using System;
using System.Globalization;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Linq;

namespace Dragon.Prototype1.Infrastructure
{
    public class ThemeViewEngine : WebFormViewEngine
    {
        public ThemeViewEngine()
        {

            base.ViewLocationFormats = new string[] {
                //theme common views
                "~/Themes/{2}/Shared/{0}.aspx",
                "~/Themes/{2}/Shared/{0}.ascx",
                //theme controller views
                "~/Themes/{2}/Views/{1}/{0}.aspx",
                "~/Themes/{2}/Views/{1}/{0}.ascx",
                //controller views
                "~/Views/{1}/{0}.aspx",
                "~/Views/{1}/{0}.ascx",
                //global shared views
                "~/Views/Shared/{0}.aspx",
                "~/Views/Shared/{0}.ascx"
            };

            base.MasterLocationFormats = new string[] {
                //theme master view
                "~/Themes/{2}/{0}.master",
                //theme controller master view
                "~/Themes/{2}/{1}/{0}.master",
                //controller master view
                "~/Views/{1}/{0}.master",
                //global master view
                "~/Views/Shared/{0}.master"
            };

            base.PartialViewLocationFormats = new string[] {
                //theme common partial views
                "~/Themes/{2}/Shared/{0}.aspx",
                "~/Themes/{2}/Shared/{0}.ascx",
                //theme controller partial views
                "~/Themes/{2}/Views/{1}/{0}.aspx",
                "~/Themes/{2}/Views/{1}/{0}.ascx",
                //controller partial views
                "~/Views/{1}/{0}.aspx",
                "~/Views/{1}/{0}.ascx",
                //global shared partial views
                "~/Views/Shared/{0}.aspx",
                "~/Views/Shared/{0}.ascx"
            };


        }

        private const string _cacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}";
        private const string _cacheKeyPrefix_Master = "Master";
        private const string _cacheKeyPrefix_Partial = "Partial";
        private const string _cacheKeyPrefix_View = "View";

        public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (String.IsNullOrEmpty(partialViewName))
            {
                throw new ArgumentException("partialViewName must be specified.", "partialViewName");

            }
            string themeName = this.GetThemeToUse(controllerContext);

            string[] searched;
            string controllerName = controllerContext.RouteData.GetRequiredString("controller");
            string partialPath = GetPath(controllerContext, PartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, themeName, controllerName, _cacheKeyPrefix_Partial, useCache, out searched);

            if (String.IsNullOrEmpty(partialPath))
            {
                return new ViewEngineResult(searched);
            }

            return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);
        }

        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (String.IsNullOrEmpty(viewName))
            {
                throw new ArgumentException("viewName must be specified.", "viewName");
            }

            string[] viewLocationsSearched;
            string[] masterLocationsSearched;

            string themeName = this.GetThemeToUse(controllerContext);

            string controllerName = controllerContext.RouteData.GetRequiredString("controller");
            string viewPath = GetPath(controllerContext, ViewLocationFormats, "ViewLocationFormats", viewName, themeName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);
            string masterPath = GetPath(controllerContext, MasterLocationFormats, "MasterLocationFormats", masterName, themeName, controllerName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched);

            if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
            {
                return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
            }

            return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
        }
        private static readonly string[] _emptyLocations;

        private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName,
        string name, string themeName, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
        {
            searchedLocations = _emptyLocations;
            if (string.IsNullOrEmpty(name))
            {
                return string.Empty;
            }
            if ((locations == null) || (locations.Length == 0))
            {
                throw new InvalidOperationException("locations must not be null or emtpy.");
            }

            bool flag = IsSpecificPath(name);
            string key = this.CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, themeName);
            if (useCache)
            {
                string viewLocation = this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
                if (viewLocation != null)
                {
                    return viewLocation;
                }
            }
            if (!flag)
            {
                return this.GetPathFromGeneralName(controllerContext, locations, name, controllerName, themeName, key, ref searchedLocations);
            }
            return this.GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
        }

        private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name,
        string controllerName, string themeName, string cacheKey, ref string[] searchedLocations)
        {
            string virtualPath = string.Empty;
            searchedLocations = new string[locations.Length];
            for (int i = 0; i < locations.Length; i++)
            {
                string str2 = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { name, controllerName, themeName });

                //if (this.FileExists(controllerContext, str2))
                if (this.VirtualPathProvider.FileExists(str2))
                {
                    searchedLocations = _emptyLocations;
                    virtualPath = str2;
                    this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
                    return virtualPath;
                }
                searchedLocations[i] = str2;
            }
            return virtualPath;
        }

        private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
        {
            string virtualPath = name;
            if (!this.FileExists(controllerContext, name))
            {
                virtualPath = string.Empty;
                searchedLocations = new string[] { name };
            }
            this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
            return virtualPath;
        }


        private static bool IsSpecificPath(string name)
        {
            char c = name[0];
            return (c == '~' || c == '/');
        }


        private string CreateCacheKey(string prefix, string name, string controllerName, string themeName)
        {
            return string.Format(CultureInfo.InvariantCulture,
                _cacheKeyFormat,
                GetType().AssemblyQualifiedName, prefix, name, controllerName, themeName);
        }



        private string GetThemeToUse(ControllerContext controllerContext)
        {
            string themeName = controllerContext.HttpContext.Items["themeName"] as string;
            if (themeName == null) themeName = "Default";
            return themeName;
        }

    }
}